package system;

import java.math.BigDecimal;
import java.math.MathContext;
import java.util.LinkedList;
import java.util.Random;

import org.uncommons.maths.random.ExponentialGenerator;

import item.Samochod;
import kolejka.Kolejka;
import stanowisko.Stanowisko;
import utils.Logger;

public class SystemUniwersalny {

	private ExponentialGenerator exponentialGenerator;
	private Logger log;
	private int liczbaSamochodow;
	private int pojemnoscSystemu;
	private int liczbaSamochodowObsluzonych = 0;
	private double czestotliwoscPojawianiaSieZgloszen;

	private LinkedList<Stanowisko<Samochod>> myjnie;
	private Kolejka<Samochod> kolejkaSamochodow;
	private LinkedList<Samochod> listaSamochodowDoPrzybycia;

	public SystemUniwersalny(LinkedList<Stanowisko<Samochod>> myjnie, Kolejka<Samochod> kolejkaSamochodow,
			double lambda, int liczbaSamochodow, int pojemnoscSystemu, boolean czyLogowac) {
		this.myjnie = myjnie;
		this.kolejkaSamochodow = kolejkaSamochodow;
		this.liczbaSamochodow = liczbaSamochodow;
		this.exponentialGenerator = new ExponentialGenerator(lambda, new Random(System.currentTimeMillis()));
		this.pojemnoscSystemu = pojemnoscSystemu;
		this.log = new Logger(!czyLogowac);
	}

	public double symulacja() {

		double lacznyCzasCzekaniaWKolejce = 0;
		double aktualnyCzas = 0;
		double czasOstatniegoZdarzenia = 0;

		this.listaSamochodowDoPrzybycia = generujlisteSamochodowDoPrzybycia(liczbaSamochodow);

		while (!listaSamochodowDoPrzybycia.isEmpty() || !this.kolejkaSamochodow.czyPusty()
				|| !czyWszystkieMyjniePuste()) {

			umiescSamochodyWKolejce(aktualnyCzas);

			for (Stanowisko<Samochod> myjnia : this.myjnie) {//usun te obiekty ktore mozna usunac
				if (!myjnia.czyPusty()
						&& aktualnyCzas >= myjnia.dajCzasRozpoczeciaMyciaSamochodu() + myjnia.dajCzasObslugi()) {
					lacznyCzasCzekaniaWKolejce += myjnia.dajCzasRozpoczeciaMyciaSamochodu()
							- myjnia.dajAktualnyObiekt().dajCzasPrzybycia();
					this.log.logCarWashed(aktualnyCzas, myjnia.dajId(), myjnia.dajAktualnyObiekt(),
							kolejkaSamochodow.dajWszystkie());
					myjnia.usunObiekt();
				}
			}

			for (Stanowisko<Samochod> myjnia : this.myjnie) {
				if (myjnia.czyPusty() && !this.kolejkaSamochodow.czyPusty()) {

					Samochod samochodDoMycia = this.kolejkaSamochodow.usunPierwszy();
					myjnia.ustawAktualnyObiekt(samochodDoMycia);

					myjnia.ustawCzasRozpoczeciaMyciaSamochodu(aktualnyCzas);

					this.log.logCarStartedToWash(aktualnyCzas, myjnia.dajId(), myjnia.dajAktualnyObiekt(),
							kolejkaSamochodow.dajWszystkie());
					break;
				}
			}

			czasOstatniegoZdarzenia = aktualnyCzas;
			aktualnyCzas = dajCzasNajblizszegoZdarzenia();
		}

		double sredniCzasOczekiwaniaWKolejce = lacznyCzasCzekaniaWKolejce / (double) liczbaSamochodow;

		System.out.println("Srednia liczba zgloszen w kolejce (Nb): " + czestotliwoscPojawianiaSieZgloszen
				* sredniCzasOczekiwaniaWKolejce);
		System.out.println("Sredni czas oczekiwania w kolejce (Tb): " + sredniCzasOczekiwaniaWKolejce);
		System.out.println("Wzgledna zdolnosc obslugi systemu (q): " + liczbaSamochodowObsluzonych
				/ (double) liczbaSamochodow);
		System.out.println("Bezwzgledna zdolnosc obslugi systemu (A): " + liczbaSamochodowObsluzonych
				/ czasOstatniegoZdarzenia);

		return lacznyCzasCzekaniaWKolejce / (double) liczbaSamochodow;
	}

	private boolean czyWszystkieMyjniePuste() {
		for (Stanowisko<Samochod> myjnia : this.myjnie) {
			if (!myjnia.czyPusty()) {
				return false;
			}
		}
		return true;
	}

	private LinkedList<Samochod> generujlisteSamochodowDoPrzybycia(int liczbaSamochodow) {
		LinkedList<Samochod> listaSamochodowDoPrzybycia = new LinkedList<Samochod>();
		double ostatniCzasPrzybycia = 0;

		for (int i = 1; i <= liczbaSamochodow; i++) {
			ostatniCzasPrzybycia += this.exponentialGenerator.nextValue();
			listaSamochodowDoPrzybycia.add(new Samochod(i, ostatniCzasPrzybycia));
		}
		this.czestotliwoscPojawianiaSieZgloszen = (double) liczbaSamochodow / ostatniCzasPrzybycia;

		return listaSamochodowDoPrzybycia;
	}

	private void umiescSamochodyWKolejce(double aktualnyCzas) {

		while (!listaSamochodowDoPrzybycia.isEmpty()
				&& listaSamochodowDoPrzybycia.getFirst().dajCzasPrzybycia() <= aktualnyCzas) {

			Samochod dodawanySamochod = listaSamochodowDoPrzybycia.removeFirst();

			if (pojemnoscSystemu == 0 || this.kolejkaSamochodow.rozmiar() < pojemnoscSystemu - 1) {
				this.kolejkaSamochodow.dodaj(dodawanySamochod);
				this.liczbaSamochodowObsluzonych++;
			}

			this.log.logAddItemToQueue(dodawanySamochod.dajCzasPrzybycia(), dodawanySamochod,
					this.kolejkaSamochodow.dajWszystkie());
		}
	}

	private double dajCzasNajblizszegoZdarzenia() {
		double czasNajblizszegoZdarzenia = Double.MAX_VALUE;

		if (!this.listaSamochodowDoPrzybycia.isEmpty()) {
			czasNajblizszegoZdarzenia = this.listaSamochodowDoPrzybycia.getFirst().dajCzasPrzybycia();
		}

		for (Stanowisko<Samochod> myjnia : this.myjnie) {
			if (!myjnia.czyPusty()
					&& myjnia.dajCzasRozpoczeciaMyciaSamochodu() + myjnia.dajCzasObslugi() < czasNajblizszegoZdarzenia) {
				czasNajblizszegoZdarzenia = myjnia.dajCzasRozpoczeciaMyciaSamochodu() + myjnia.dajCzasObslugi();
			}
		}
		return czasNajblizszegoZdarzenia;
	}

}
